﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using System.Runtime.Serialization;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;


/// <summary>
/// Summary description for SerializableEntity
/// </summary>
[Serializable]
[XmlSchemaProvider("MySchema")]
public class SerializableEntity<T> : IXmlSerializable, ISerializable where T : class, new()
{
    private static readonly string ns = "http://tempuri.org/";
    private static readonly string tns = "http://tempuri.org/";
    private static readonly string xmlns = "http://www.w3.org/2001/XMLSchema";
    private static readonly string mstypes = "http://microsoft.com/wsdl/types/";

    public SerializableEntity()
    {
    }

    public SerializableEntity(T entity)
    {
        Entity = entity;
    }

    private T _entity;

    public T Entity
    {
        get { return _entity; }
        set { _entity = value; }
    }

    #region IXmlSerializable Members

    

    public XmlSchema GetSchema()
    {
        return null;
    }

    // This is the method named by the XmlSchemaProviderAttribute applied to the type.
    public static XmlQualifiedName MySchema2(XmlSchemaSet xs)
    {
        // This method is called by the framework to get the schema for this type.
        var sb = new System.Text.StringBuilder();
        var sw = new System.IO.StringWriter(sb);
        var xw = new XmlTextWriter(sw);

        // generate the schema for type T
        xw.WriteStartDocument();
        xw.WriteStartElement("schema");
        xw.WriteAttributeString("targetNamespace", "http://tempuri.org/");
        xw.WriteAttributeString("xmlns", "http://www.w3.org/2001/XMLSchema");
        xw.WriteStartElement("complexType");
        xw.WriteAttributeString("name", typeof (T).Name);
        xw.WriteStartElement("sequence");

        PropertyInfo[] infos = typeof (T).GetProperties();
        foreach (PropertyInfo pi in infos)
        {
            bool isAssociation = false;
            foreach (object a in pi.GetCustomAttributes(true))
            {
                //check whether the property is an Association
                if (a.GetType() == typeof (System.Data.Linq.Mapping.AssociationAttribute))
                {
                    isAssociation = true;
                    break;
                }
            }
            //only use the property which is not an Association
            if (!isAssociation)
            {
                xw.WriteStartElement("element");
                xw.WriteAttributeString("name", pi.Name);
                if (pi.PropertyType.IsGenericType)
                {
                    Type[] types = pi.PropertyType.GetGenericArguments();
                    xw.WriteAttributeString("type", "" + GetXsdType(types[0].FullName));
                }
                else
                {
                    xw.WriteAttributeString("type", "" + GetXsdType(pi.PropertyType.FullName));
                }
                xw.WriteEndElement();
            }
        }
        xw.WriteEndElement();
        xw.WriteEndElement();
        xw.WriteEndElement();
        xw.WriteEndDocument();
        xw.Close();

        var schemaSerializer = new XmlSerializer(typeof (XmlSchema));
        var sr = new System.IO.StringReader(sb.ToString());
        var s = (XmlSchema) schemaSerializer.Deserialize(sr);
        xs.XmlResolver = new XmlUrlResolver();
        xs.Add(s);

        return new XmlQualifiedName(typeof (T).Name, ns);
    }

    public void ReadXml(XmlReader reader)
    {
        _entity = new T();
        PropertyInfo[] pinfos = _entity.GetType().GetProperties();

        if (reader.LocalName == typeof (T).Name)
        {
            reader.MoveToContent();
            string inn = reader.ReadOuterXml();
            var sr = new System.IO.StringReader(inn);
            var tr = new XmlTextReader(sr);
            tr.Read();
            while (tr.Read())
            {
                string elementName = tr.LocalName;
                string value = tr.ReadString();
                //tr.Read();
                foreach (PropertyInfo pi in pinfos)
                {
                    if (pi.Name == elementName)
                    {
                        TypeConverter tc = TypeDescriptor.GetConverter(pi.PropertyType);
                        pi.SetValue(_entity, tc.ConvertFromString(value), null);
                    }
                }
            }
            //<ProductID>1</ProductID><ProductName>Chai</ProductName><SupplierID>1</SupplierID><CategoryID>1</CategoryID><QuantityPerUnit>10 boxes x 20 bags</QuantityPerUnit><UnitPrice>18.0000</UnitPrice><UnitsInStock>39</UnitsInStock><UnitsOnOrder>0</UnitsOnOrder><ReorderLevel>10</ReorderLevel><Discontinued>false</Discontinued>
        }

        //while (reader.Read())
        //{
        //    if (reader.IsStartElement())
        //    {
        //        string elementName = reader.LocalName;
        //        string value = reader.ReadString();
        //        foreach (PropertyInfo pi in pinfos)
        //        {
        //            if (pi.Name == elementName)
        //            {
        //                TypeConverter tc = TypeDescriptor.GetConverter(pi.PropertyType);
        //                pi.SetValue(_entity, tc.ConvertFromString(value), null);
        //            }
        //        }
        //    }
        //}
    }

    public void WriteXml2(XmlWriter writer)
    {
        PropertyInfo[] pinfos = _entity.GetType().GetProperties();

        foreach (PropertyInfo pi in pinfos)
        {
            bool isAssociation = false;
            foreach (object obj in pi.GetCustomAttributes(true))
            {
                if (obj.GetType() == typeof (System.Data.Linq.Mapping.AssociationAttribute))
                {
                    isAssociation = true;
                    break;
                }
            }
            if (!isAssociation)
            {
                if (pi.GetValue(_entity, null) != null)
                {
                    writer.WriteStartElement(pi.Name);
                    writer.WriteValue(pi.GetValue(_entity, null));
                    writer.WriteEndElement();
                }
            }
        }
    }
    
    public static XmlQualifiedName MySchema(XmlSchemaSet xs)
    {
        var schema = new XmlSchema {TargetNamespace = tns, ElementFormDefault = XmlSchemaForm.Qualified};

        schema.Namespaces.Add("s1", mstypes);

        var entity = new XmlSchemaComplexType {Name = typeof (T).Name};
        schema.Items.Add(entity);

        var sequence = new XmlSchemaSequence();
        entity.Particle = sequence;

        bool includeMsTypes = false;

        PropertyInfo[] infos = typeof (T).GetProperties();
        foreach (PropertyInfo pi in infos)
        {
            bool isAssociation = false;
            foreach (object a in pi.GetCustomAttributes(true))
            {
//check whether the property is an Association
                if (a.GetType() == typeof (System.Data.Linq.Mapping.AssociationAttribute))
                {
                    isAssociation = true;
                    break;
                }
            }

//only use the property which is not an Association
            if (!isAssociation)
            {
                Type type = pi.PropertyType;
                var element = new XmlSchemaElement {Name = pi.Name};
                
                sequence.Items.Add(element);

                if (type.IsGenericType)
                {
                    if (type.GetGenericTypeDefinition().Equals(typeof (Nullable<>)))
                    element.IsNillable = true;

                    Type[] types = type.GetGenericArguments();
                    type = types[0];
                }

                if (type.IsEnum)
                {
                    element.SchemaTypeName = new XmlQualifiedName(type.Name, tns);

                    if (schema.SchemaTypes[element.SchemaTypeName] == null)
                    {
                        var simpleType = new XmlSchemaSimpleType {Name = type.Name};

                        var restriction = new XmlSchemaSimpleTypeRestriction
                                              {
                                                  BaseTypeName = new XmlQualifiedName("string", xmlns)
                                              };
                        simpleType.Content = restriction;

                        foreach (string name in Enum.GetNames(type))
                            restriction.Facets.Add(new XmlSchemaEnumerationFacet {Value = name});

                        schema.Items.Add(simpleType);
                    }
                }
                else
                    element.SchemaTypeName = GetXsdType(type.FullName);

                if (type.IsClass)
                {
                    element.MinOccurs = 0;
                    element.MaxOccurs = 1;
                }
                else
                {
                    element.MinOccurs = 1;
                    element.MaxOccurs = 1;
                }

                if (element.SchemaTypeName.Namespace == mstypes)
                    includeMsTypes = true;
            }
        }

        if (includeMsTypes)
        {
            var msshema = new XmlSchema {TargetNamespace = mstypes, ElementFormDefault = XmlSchemaForm.Qualified};

            var simpleType = new XmlSchemaSimpleType {Name = "guid"};

            var restriction = new XmlSchemaSimpleTypeRestriction
                                  {
                                      BaseTypeName = new XmlQualifiedName("string", xmlns)
                                  };
            simpleType.Content = restriction;

            restriction.Facets.Add(new XmlSchemaPatternFacet
                                       {
                                           Value =
                                               "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"
                                       });

            msshema.Items.Add(simpleType);

            xs.Add(msshema);
        }

        xs.Add(schema);
        return new XmlQualifiedName(typeof (T).Name, tns);
    }


    public void WriteXml(XmlWriter writer)
    {
        writer.WriteAttributeString("xmlns", "xsi", null, "http://www.w3.org/2001/XMLSchema-instance");
        writer.WriteAttributeString("xmlns", "xsd", null, "http://www.w3.org/2001/XMLSchema");

        PropertyInfo[] pinfos = _entity.GetType().GetProperties();

        foreach (PropertyInfo pi in pinfos)
        {
            bool isAssociation = false;
            foreach (object obj in pi.GetCustomAttributes(true))
            {
                if (obj.GetType() == typeof (System.Data.Linq.Mapping.AssociationAttribute))
                {
                    isAssociation = true;
                    break;
                }
            }
            if (!isAssociation)
            {
                if (pi.GetValue(_entity, null) != null)
                {
                    writer.WriteStartElement(pi.Name);

                    if (pi.PropertyType == typeof (Guid) || pi.PropertyType.IsEnum)
                        writer.WriteValue(pi.GetValue(_entity, null).ToString());
                    else
                        writer.WriteValue(pi.GetValue(_entity, null));

                    writer.WriteEndElement();
                }
                else
                {
                    //if (pi.PropertyType.IsGenericType &&
                    //    pi.PropertyType.GetGenericTypeDefinition().Equals(typeof (Nullable<>)))
                    //{
                    //    writer.WriteStartElement(pi.Name);
                    //    writer.WriteAttributeString("nil", "http://www.w3.org/2001/XMLSchema-instance", "true");
                    //    writer.WriteEndElement();
                    //}

                        writer.WriteStartElement(pi.Name);
                        writer.WriteAttributeString("nil", "true");
                        writer.WriteEndElement();
                }
            }
        }
    }


    private static XmlQualifiedName GetXsdType(string nativeType)
    {
        var xsdTypes = new Dictionary<string,XmlQualifiedName>
                           {
                               {"System.Boolean", new XmlQualifiedName("boolean", xmlns)},
                               {"System.Byte", new XmlQualifiedName("unsignedByte", xmlns)},
                               {"System.DateTime", new XmlQualifiedName("dateTime", xmlns)},
                               {"System.Decimal", new XmlQualifiedName("decimal", xmlns)},
                               {"System.Double", new XmlQualifiedName("double", xmlns)},
                               {"System.Int16", new XmlQualifiedName("short", xmlns)},
                               {"System.Int32", new XmlQualifiedName("int", xmlns)},
                               {"System.Int64", new XmlQualifiedName("long", xmlns)},
                               {"System.SByte", new XmlQualifiedName("byte", xmlns)},
                               {"System.Single", new XmlQualifiedName("float", xmlns)},
                               {"System.String", new XmlQualifiedName("string", xmlns)},
                               {"System.UInt16", new XmlQualifiedName("unsignedShort", xmlns)},
                               {"System.UInt32", new XmlQualifiedName("unsignedInt", xmlns)},
                               {"System.UInt64", new XmlQualifiedName("unsignedLong", xmlns)},
                               {"System.Uri", new XmlQualifiedName("anyURI", xmlns)},
                               {"System.Guid", new XmlQualifiedName("guid", mstypes)}
                           };

        if (xsdTypes.ContainsKey(nativeType))
            return xsdTypes[nativeType];
        return null;
    }

/*
    private static string GetXsdType2(string nativeType)
    {
        var xsdTypes = new string[]
                                {
                                    "boolean",
                                    "unsignedByte",
                                    "dateTime",
                                    "decimal",
                                    "Double",
                                    "short",
                                    "int",
                                    "long",
                                    "Byte",
                                    "Float",
                                    "string",
                                    "unsignedShort",
                                    "unsignedInt",
                                    "unsignedLong",
                                    "anyURI"
                                };
        var nativeTypes = new string[]
                                   {
                                       "System.Boolean",
                                       "System.Byte",
                                       "System.DateTime",
                                       "System.Decimal",
                                       "System.Double",
                                       "System.Int16",
                                       "System.Int32",
                                       "System.Int64",
                                       "System.SByte",
                                       "System.Single",
                                       "System.String",
                                       "System.UInt16",
                                       "System.UInt32",
                                       "System.UInt64",
                                       "System.Uri"
                                   };

        for (int i = 0; i < nativeTypes.Length; i++)
        {
            if (nativeType == nativeTypes[i])
            {
                return xsdTypes[i];
            }
        }
        return "";
    }
*/

    #endregion

    #region ISerializable Members

    public SerializableEntity(SerializationInfo info, StreamingContext context)
    {
        _entity = new T();
        PropertyInfo[] properties = _entity.GetType().GetProperties();

        SerializationInfoEnumerator enumerator = info.GetEnumerator();

        while (enumerator.MoveNext())
        {
            SerializationEntry se = enumerator.Current;
            foreach (PropertyInfo pi in properties)
            {
                if (pi.Name == se.Name && pi.CanWrite)
                {
                    pi.SetValue(_entity, info.GetValue(se.Name, pi.PropertyType), null);
                }
            }
        }
    }

    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        PropertyInfo[] infos = _entity.GetType().GetProperties();

        foreach (PropertyInfo pi in infos)
        {
            bool isAssociation = false;
            foreach (object obj in pi.GetCustomAttributes(true))
            {
                if (obj.GetType() == typeof (System.Data.Linq.Mapping.AssociationAttribute))
                {
                    isAssociation = true;
                    break;
                }
            }
            if (!isAssociation)
            {
                if (pi.GetValue(_entity, null) != null)
                {
                    info.AddValue(pi.Name, pi.GetValue(_entity, null));
                }
            }
        }
    }

    #endregion
}